package baidu

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"gitee.com/xslasd/go-map/ls"
)

const (
	// 地理编码服务
	GeocoderUrl = "http://api.map.baidu.com/geocoding/v3"
	// 全球逆地理编码服务Url
	ReverseGeocodingUrl = "http://api.map.baidu.com/reverse_geocoding/v3"
	// 普通IP定位
	IPLocationUrl = "http://api.map.baidu.com/location/ip"
	// 智能硬件定位
	LBSLocationUrl = "https://api.map.baidu.com/locapi/v2"
	// 天气
	WeatherUrl = "http://api.map.baidu.com/weather/v1/"
)

type BaiduMapSDK struct {
	ak           string
	output       string
	coordType    string
	retCoordtype string
}

func NewBaiduMapSDK(ak, output, coordType, retCoordtype string) *BaiduMapSDK {
	return &BaiduMapSDK{ak, output, coordType, retCoordtype}
}

func DefaultBaiduMapSDK(ak string) *BaiduMapSDK {
	return &BaiduMapSDK{ak, "json", "gcj02ll", "gcj02ll"}
}

func (s *BaiduMapSDK) Geocoder(address string, city string) (*ls.Location, error) {
	params := url.Values{}
	params.Set("address", address)
	params.Set("city", city)
	params.Set("ret_coordtype", s.retCoordtype)
	return s.GeocoderParams(&params)
}

func (s *BaiduMapSDK) GeocoderParams(params *url.Values) (*ls.Location, error) {
	Url, _ := url.Parse(GeocoderUrl)
	params.Set("ak", s.ak)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	log.Printf("%s\n", body)
	var out DataRes
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != 0 {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	data, err := json.Marshal(out.Result)
	if err != nil {
		return nil, err
	}
	var obj GeocoderRes
	err = json.Unmarshal(data, &obj)
	if err != nil {
		return nil, err
	}
	res := &ls.Location{
		Lng: obj.Location.Lng,
		Lat: obj.Location.Lat,
	}
	return res, nil
}

func (s *BaiduMapSDK) ReverseGeocoding(lng, lat float64) (*ls.Address, error) {
	params := url.Values{}
	params.Set("location", fmt.Sprintf("%v,%v", lat, lng))
	params.Set("coordtype", s.coordType)
	params.Set("ret_coordtype", s.retCoordtype)
	return s.ReverseGeocodingParams(params)
}

func (s *BaiduMapSDK) ReverseGeocodingParams(params url.Values) (*ls.Address, error) {
	Url, _ := url.Parse(ReverseGeocodingUrl)
	params.Set("ak", s.ak)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	log.Printf("ReverseGeocodingParams %s\n", body)
	var out DataRes
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != 0 {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	data, err := json.Marshal(out.Result)
	if err != nil {
		return nil, err
	}
	var obj ReverseGeocodingRes
	err = json.Unmarshal(data, &obj)
	if err != nil {
		return nil, err
	}
	city := obj.AddressComponent.City
	if obj.AddressComponent.Province == obj.AddressComponent.City {
		city = ""
	}
	res := &ls.Address{
		FormattedAddress: obj.FormattedAddress,
		Province:         obj.AddressComponent.Province,
		City:             city,
		District:         obj.AddressComponent.District,
		Town:             obj.AddressComponent.Town,
		TownCode:         obj.AddressComponent.TownCode,
		Street:           obj.AddressComponent.Street,
		StreetNumber:     obj.AddressComponent.StreetNumber,
	}
	adcode, err := strconv.ParseInt(obj.AddressComponent.AdCode, 10, 64)
	if err != nil {
		return nil, err
	}
	res.AdCode = adcode
	pois := make([]*ls.PoiInfo, 0)
	for _, item := range obj.Pois {
		poi := &ls.PoiInfo{
			UID:       item.UID,
			Addr:      item.Addr,
			Direction: item.Direction,
			Distance:  item.Distance,
			Name:      item.Name,
			Tag:       item.Tag,
			Tel:       item.Tel,
		}
		poi.Location.Lng = item.Point.X
		poi.Location.Lat = item.Point.Y
		pois = append(pois, poi)
	}
	res.Pois = pois
	return res, nil
}

func (s *BaiduMapSDK) IPLocation(ip string) (*ls.IPPosition, error) {
	params := url.Values{}
	params.Set("ip", ip)
	params.Set("coor", s.retCoordtype)
	return s.IPLocationParams(params)
}

func (s *BaiduMapSDK) IPLocationParams(params url.Values) (*ls.IPPosition, error) {
	Url, _ := url.Parse(IPLocationUrl)
	params.Set("ak", s.ak)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var out IPLocationRes
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != 0 {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	res := &ls.IPPosition{
		Province: out.Content.AddressDetail.Province,
		City:     out.Content.AddressDetail.City,
	}
	return res, nil
}

func (s *BaiduMapSDK) LBSLocation(req *ls.LBSInfo) (*ls.LBSPosition, error) {
	params := LBSLocationReq{
		Key:   s.ak,
		Src:   "gomap",
		Prod:  "gomap",
		Ver:   "1.0",
		Trace: false,
		Body: []LBSRequestBody{
			{
				Accesstype: req.AccessType,
				IMEI:       req.IMEI,
				IMSI:       req.IMEI,
				CDMA:       req.CDMA,
				Network:    req.Network,
				BTS:        req.BTS,
				SMAC:       req.ClientIP,
				ClientIP:   req.ClientIP,
				Tel:        req.TEL,
				MMAC:       req.MMAC,
				CTime:      strconv.FormatInt(time.Now().Unix(), 10),
			},
		},
	}
	return s.LBSLocationParams(params)
}

// todo: 未调用成功
func (s *BaiduMapSDK) LBSLocationParams(params LBSLocationReq) (*ls.LBSPosition, error) {
	rbody, err := json.Marshal(params)
	if err != nil {
		return nil, err
	}
	client := &http.Client{}
	reqest, err := http.NewRequest("POST", LBSLocationUrl, bytes.NewBuffer(rbody))
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(reqest)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	fmt.Println(string(body))
	var out LBSLocationRes
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != 0 {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	if len(out.Result) == 0 {
		return &ls.LBSPosition{}, nil
	}
	data := out.Result[0]
	res := &ls.LBSPosition{
		Radius:           data.Radius,
		FormattedAddress: fmt.Sprintf("%s%s%s%s", data.Province, data.City, data.District, data.Bldg),
		Province:         data.Province,
		City:             data.City,
		District:         data.District,
	}
	l := strings.Split(data.Location, ",")
	lng, err := strconv.ParseFloat(l[0], 64)
	if err != nil {
		return nil, err
	}
	lat, err := strconv.ParseFloat(l[1], 64)
	if err != nil {
		return nil, err
	}
	res.Location.Lng = lng
	res.Location.Lat = lat

	a, err := s.ReverseGeocoding(lng, lat)
	if err != nil {
		return nil, err
	}
	res.AdCode = a.AdCode

	return res, nil
}

func (s *BaiduMapSDK) GetWeatherNow(adcode int64) (*ls.WeatherInfo, error) {
	Url, _ := url.Parse(WeatherUrl)
	params := url.Values{}
	params.Set("ak", s.ak)
	params.Set("district_id", strconv.FormatInt(adcode, 10))
	params.Set("data_type", "now")
	params.Set("output", s.output)

	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	log.Printf("url: %s\n", Url.String())
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	out := new(WeatherResponse)
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}

	if out.Status != 0 {
		return nil, fmt.Errorf("error response, code: %d, info: %s", out.Status, out.Message)
	}

	layout := "20060102150405"
	t, err := time.Parse(layout, out.Result.Now.UpTime)

	if err != nil {
		return nil, err
	}

	return &ls.WeatherInfo{
		Province:      out.Result.Location.Province,
		City:          out.Result.Location.City,
		Adcode:        out.Result.Location.ID,
		Weather:       out.Result.Now.Text,
		Temperature:   strconv.FormatInt(out.Result.Now.Temp, 10),
		WindDirection: out.Result.Now.WindDir,
		WindPower:     out.Result.Now.WindClass,
		Humidity:      strconv.FormatInt(out.Result.Now.RH, 10),
		ReportTime:    t.Format("2006-01-02 15:04:05"),
	}, nil
}
